package parser

import (
	"fmt"
	"strconv"
	"strings"
)

// ConstantFolder implements constant folding optimization
type ConstantFolder struct {
	// Configuration options
	EnableArithmetic bool
	EnableString     bool
	EnableBoolean    bool
}

// NewConstantFolder creates a new constant folder with default settings
func NewConstantFolder() *ConstantFolder {
	return &ConstantFolder{
		EnableArithmetic: true,
		EnableString:     true,
		EnableBoolean:    true,
	}
}

// FoldExpression attempts to fold constant expressions
func (cf *ConstantFolder) FoldExpression(expr Expression) Expression {
	if expr == nil {
		return nil
	}

	switch e := expr.(type) {
	case *BinaryExpr:
		return cf.foldBinaryExpr(e)
	case *UnaryExpr:
		return cf.foldUnaryExpr(e)
	case *FunctionCall:
		return cf.foldFunctionCall(e)
	case *CaseExpr:
		return cf.foldCaseExpr(e)
	case *Literal:
		return e // Already a constant
	default:
		return expr
	}
}

// foldBinaryExpr folds binary expressions with constant operands
func (cf *ConstantFolder) foldBinaryExpr(expr *BinaryExpr) Expression {
	// First, recursively fold operands
	left := cf.FoldExpression(expr.Left)
	right := cf.FoldExpression(expr.Right)

	// Check if both operands are literals
	leftLit, leftIsLit := left.(*Literal)
	rightLit, rightIsLit := right.(*Literal)

	if !leftIsLit || !rightIsLit {
		// Return expression with folded operands
		return &BinaryExpr{
			Left:     left,
			Operator: expr.Operator,
			Right:    right,
		}
	}

	// Attempt to fold based on operator type
	switch expr.Operator {
	// Arithmetic operators
	case PLUS:
		return cf.foldArithmetic(leftLit, rightLit, expr.Operator)
	case MINUS:
		return cf.foldArithmetic(leftLit, rightLit, expr.Operator)
	case MULTIPLY:
		return cf.foldArithmetic(leftLit, rightLit, expr.Operator)
	case DIVIDE:
		return cf.foldArithmetic(leftLit, rightLit, expr.Operator)
	case MODULO:
		return cf.foldArithmetic(leftLit, rightLit, expr.Operator)

	// Comparison operators
	case EQ, NE, LT, LE, GT, GE:
		return cf.foldComparison(leftLit, rightLit, expr.Operator)

	// Logical operators
	case AND:
		return cf.foldLogical(leftLit, rightLit, expr.Operator)
	case OR:
		return cf.foldLogical(leftLit, rightLit, expr.Operator)

	// String concatenation
	case CONCAT:
		return cf.foldStringConcat(leftLit, rightLit)

	// Null check operators
	case IS_NULL:
		return cf.foldIsNull(leftLit)
	case IS_NOT_NULL:
		return cf.foldIsNotNull(leftLit)

	default:
		// Return expression with folded operands
		return &BinaryExpr{
			Left:     left,
			Operator: expr.Operator,
			Right:    right,
		}
	}
}

// foldUnaryExpr folds unary expressions with constant operands
func (cf *ConstantFolder) foldUnaryExpr(expr *UnaryExpr) Expression {
	operand := cf.FoldExpression(expr.Operand)
	lit, isLit := operand.(*Literal)

	if !isLit {
		return &UnaryExpr{
			Operator: expr.Operator,
			Operand:  operand,
		}
	}

	switch expr.Operator {
	case MINUS_UNARY:
		return cf.foldUnaryMinus(lit)
	case PLUS_UNARY:
		return cf.foldUnaryPlus(lit)
	case NOT:
		return cf.foldUnaryNot(lit)
	default:
		return &UnaryExpr{
			Operator: expr.Operator,
			Operand:  operand,
		}
	}
}

// foldArithmetic performs arithmetic constant folding
func (cf *ConstantFolder) foldArithmetic(left, right *Literal, op BinaryOp) Expression {
	if !cf.EnableArithmetic {
		return &BinaryExpr{Left: left, Operator: op, Right: right}
	}

	// Convert to numbers
	leftVal, leftErr := cf.literalToFloat(left)
	rightVal, rightErr := cf.literalToFloat(right)

	if leftErr != nil || rightErr != nil {
		return &BinaryExpr{Left: left, Operator: op, Right: right}
	}

	var result float64
	switch op {
	case PLUS:
		result = leftVal + rightVal
	case MINUS:
		result = leftVal - rightVal
	case MULTIPLY:
		result = leftVal * rightVal
	case DIVIDE:
		if rightVal == 0 {
			return &BinaryExpr{Left: left, Operator: op, Right: right} // Avoid division by zero
		}
		result = leftVal / rightVal
	case MODULO:
		if rightVal == 0 {
			return &BinaryExpr{Left: left, Operator: op, Right: right} // Avoid division by zero
		}
		// For modulo, convert to integers
		leftInt := int64(leftVal)
		rightInt := int64(rightVal)
		result = float64(leftInt % rightInt)
	default:
		return &BinaryExpr{Left: left, Operator: op, Right: right}
	}

	// Return as appropriate literal type
	if result == float64(int64(result)) {
		return &Literal{
			Value: strconv.FormatInt(int64(result), 10),
			Type:  IntegerLiteral,
		}
	} else {
		return &Literal{
			Value: strconv.FormatFloat(result, 'f', -1, 64),
			Type:  FloatLiteral,
		}
	}
}

// foldComparison performs comparison constant folding
func (cf *ConstantFolder) foldComparison(left, right *Literal, op BinaryOp) Expression {
	// Try numeric comparison first
	leftVal, leftErr := cf.literalToFloat(left)
	rightVal, rightErr := cf.literalToFloat(right)

	if leftErr == nil && rightErr == nil {
		return cf.compareNumbers(leftVal, rightVal, op)
	}

	// Fall back to string comparison
	leftStr, leftOk := left.Value.(string)
	rightStr, rightOk := right.Value.(string)

	if !leftOk || !rightOk {
		return &BinaryExpr{Left: left, Operator: op, Right: right}
	}

	if left.Type == StringLiteral {
		leftStr = strings.Trim(leftStr, "'\"")
	}
	if right.Type == StringLiteral {
		rightStr = strings.Trim(rightStr, "'\"")
	}

	return cf.compareStrings(leftStr, rightStr, op)
}

// compareNumbers compares two numeric values
func (cf *ConstantFolder) compareNumbers(left, right float64, op BinaryOp) *Literal {
	var result bool
	switch op {
	case EQ:
		result = left == right
	case NE:
		result = left != right
	case LT:
		result = left < right
	case LE:
		result = left <= right
	case GT:
		result = left > right
	case GE:
		result = left >= right
	default:
		return &Literal{Value: "false", Type: BooleanLiteral}
	}

	if result {
		return &Literal{Value: "true", Type: BooleanLiteral}
	} else {
		return &Literal{Value: "false", Type: BooleanLiteral}
	}
}

// compareStrings compares two string values
func (cf *ConstantFolder) compareStrings(left, right string, op BinaryOp) *Literal {
	var result bool
	switch op {
	case EQ:
		result = left == right
	case NE:
		result = left != right
	case LT:
		result = left < right
	case LE:
		result = left <= right
	case GT:
		result = left > right
	case GE:
		result = left >= right
	default:
		return &Literal{Value: "false", Type: BooleanLiteral}
	}

	if result {
		return &Literal{Value: "true", Type: BooleanLiteral}
	} else {
		return &Literal{Value: "false", Type: BooleanLiteral}
	}
}

// foldLogical performs logical constant folding
func (cf *ConstantFolder) foldLogical(left, right *Literal, op BinaryOp) Expression {
	if !cf.EnableBoolean {
		return &BinaryExpr{Left: left, Operator: op, Right: right}
	}

	leftBool := cf.literalToBool(left)
	rightBool := cf.literalToBool(right)

	var result bool
	switch op {
	case AND:
		result = leftBool && rightBool
	case OR:
		result = leftBool || rightBool
	default:
		return &BinaryExpr{Left: left, Operator: op, Right: right}
	}

	if result {
		return &Literal{Value: "true", Type: BooleanLiteral}
	} else {
		return &Literal{Value: "false", Type: BooleanLiteral}
	}
}

// foldStringConcat performs string concatenation constant folding
func (cf *ConstantFolder) foldStringConcat(left, right *Literal) Expression {
	// Convert both operands to strings
	leftStr := cf.literalToString(left)
	rightStr := cf.literalToString(right)
	
	// Concatenate the strings
	result := leftStr + rightStr
	
	return &Literal{
		Value: "'" + result + "'",
		Type:  StringLiteral,
	}
}

// foldIsNull performs IS NULL constant folding
func (cf *ConstantFolder) foldIsNull(operand *Literal) Expression {
	// Check if the operand is null
	isNull := operand.Type == NullLiteral || operand.Value == nil
	
	if isNull {
		return &Literal{Value: "true", Type: BooleanLiteral}
	} else {
		return &Literal{Value: "false", Type: BooleanLiteral}
	}
}

// foldIsNotNull performs IS NOT NULL constant folding
func (cf *ConstantFolder) foldIsNotNull(operand *Literal) Expression {
	// Check if the operand is null
	isNull := operand.Type == NullLiteral || operand.Value == nil
	
	if isNull {
		return &Literal{Value: "false", Type: BooleanLiteral}
	} else {
		return &Literal{Value: "true", Type: BooleanLiteral}
	}
}

// foldUnaryMinus folds unary minus operations
func (cf *ConstantFolder) foldUnaryMinus(lit *Literal) Expression {
	val, err := cf.literalToFloat(lit)
	if err != nil {
		return &UnaryExpr{Operator: MINUS_UNARY, Operand: lit}
	}

	result := -val
	if result == float64(int64(result)) {
		return &Literal{
			Value: strconv.FormatInt(int64(result), 10),
			Type:  IntegerLiteral,
		}
	} else {
		return &Literal{
			Value: strconv.FormatFloat(result, 'f', -1, 64),
			Type:  FloatLiteral,
		}
	}
}

// foldUnaryPlus folds unary plus operations
func (cf *ConstantFolder) foldUnaryPlus(lit *Literal) Expression {
	_, err := cf.literalToFloat(lit)
	if err != nil {
		return &UnaryExpr{Operator: PLUS_UNARY, Operand: lit}
	}
	// Unary plus doesn't change the value
	return lit
}

// foldUnaryNot folds unary NOT operations
func (cf *ConstantFolder) foldUnaryNot(lit *Literal) Expression {
	if !cf.EnableBoolean {
		return &UnaryExpr{Operator: NOT, Operand: lit}
	}

	boolVal := cf.literalToBool(lit)
	if !boolVal {
		return &Literal{Value: "true", Type: BooleanLiteral}
	} else {
		return &Literal{Value: "false", Type: BooleanLiteral}
	}
}

// foldFunctionCall attempts to fold function calls with constant arguments
func (cf *ConstantFolder) foldFunctionCall(expr *FunctionCall) Expression {
	// Fold arguments first
	foldedArgs := make([]Expression, len(expr.Args))
	allConstant := true

	for i, arg := range expr.Args {
		foldedArgs[i] = cf.FoldExpression(arg)
		if _, isLit := foldedArgs[i].(*Literal); !isLit {
			allConstant = false
		}
	}

	// If not all arguments are constants, return with folded args
	if !allConstant {
		return &FunctionCall{
			Name:     expr.Name,
			Args:     foldedArgs,
			Distinct: expr.Distinct,
			Over:     expr.Over,
		}
	}

	// Attempt to fold specific functions
	switch strings.ToUpper(expr.Name) {
	case "COALESCE":
		return cf.foldCoalesce(foldedArgs)
	case "NULLIF":
		return cf.foldNullIf(foldedArgs)
	case "GREATEST":
		return cf.foldGreatest(foldedArgs)
	case "LEAST":
		return cf.foldLeast(foldedArgs)
	default:
		// Return with folded arguments
		return &FunctionCall{
			Name:     expr.Name,
			Args:     foldedArgs,
			Distinct: expr.Distinct,
			Over:     expr.Over,
		}
	}
}

// foldCaseExpr attempts to fold CASE expressions
func (cf *ConstantFolder) foldCaseExpr(expr *CaseExpr) Expression {
	// Fold the test expression if present
	var testExpr Expression
	if expr.Expr != nil {
		testExpr = cf.FoldExpression(expr.Expr)
	}

	// Fold WHEN clauses
	foldedWhenList := make([]*WhenClause, len(expr.WhenList))
	for i, when := range expr.WhenList {
		foldedWhenList[i] = &WhenClause{
			Condition: cf.FoldExpression(when.Condition),
			Result:    cf.FoldExpression(when.Result),
		}
	}

	// Fold ELSE clause
	var elseExpr Expression
	if expr.Else != nil {
		elseExpr = cf.FoldExpression(expr.Else)
	}

	// Try to evaluate the CASE expression
	if testExpr != nil {
		// Simple CASE: CASE expr WHEN value THEN result
		if testLit, isLit := testExpr.(*Literal); isLit {
			for _, when := range foldedWhenList {
				if whenLit, isWhenLit := when.Condition.(*Literal); isWhenLit {
					if cf.literalsEqual(testLit, whenLit) {
						return when.Result
					}
				}
			}
			// No WHEN clause matched, return ELSE
			if elseExpr != nil {
				return elseExpr
			}
		}
	} else {
		// Searched CASE: CASE WHEN condition THEN result
		for _, when := range foldedWhenList {
			if condLit, isCondLit := when.Condition.(*Literal); isCondLit {
				if cf.literalToBool(condLit) {
					return when.Result
				}
			}
		}
		// No WHEN condition was true, return ELSE
		if elseExpr != nil {
			return elseExpr
		}
	}

	// Return CASE with folded expressions
	return &CaseExpr{
		Expr:     testExpr,
		WhenList: foldedWhenList,
		Else:     elseExpr,
	}
}

// Helper functions

// literalToFloat converts a literal to float64
func (cf *ConstantFolder) literalToFloat(lit *Literal) (float64, error) {
	switch lit.Type {
	case IntegerLiteral:
		// Handle different value types for integers
		if val, ok := lit.Value.(int64); ok {
			return float64(val), nil
		}
		if val, ok := lit.Value.(int); ok {
			return float64(val), nil
		}
		if val, ok := lit.Value.(float64); ok {
			return val, nil
		}
		if str, ok := lit.Value.(string); ok {
			return strconv.ParseFloat(str, 64)
		}
		return 0, fmt.Errorf("invalid integer literal value type: %T", lit.Value)
	case FloatLiteral:
		// Handle different value types for floats
		if val, ok := lit.Value.(float64); ok {
			return val, nil
		}
		if val, ok := lit.Value.(float32); ok {
			return float64(val), nil
		}
		if str, ok := lit.Value.(string); ok {
			return strconv.ParseFloat(str, 64)
		}
		return 0, fmt.Errorf("invalid float literal value type: %T", lit.Value)
	default:
		return 0, fmt.Errorf("not a numeric literal")
	}
}

// literalToBool converts a literal to boolean
func (cf *ConstantFolder) literalToBool(lit *Literal) bool {
	switch lit.Type {
	case BooleanLiteral:
		if val, ok := lit.Value.(bool); ok {
			return val
		}
		if str, ok := lit.Value.(string); ok {
			return strings.ToLower(str) == "true"
		}
		return false
	case IntegerLiteral:
		// Handle different integer value types
		if val, ok := lit.Value.(int64); ok {
			return val != 0
		}
		if val, ok := lit.Value.(int); ok {
			return val != 0
		}
		if str, ok := lit.Value.(string); ok {
			val, _ := strconv.ParseInt(str, 10, 64)
			return val != 0
		}
		return false
	case FloatLiteral:
		// Handle different float value types
		if val, ok := lit.Value.(float64); ok {
			return val != 0.0
		}
		if val, ok := lit.Value.(float32); ok {
			return val != 0.0
		}
		if str, ok := lit.Value.(string); ok {
			val, _ := strconv.ParseFloat(str, 64)
			return val != 0.0
		}
		return false
	case StringLiteral:
		if str, ok := lit.Value.(string); ok {
			val := strings.Trim(str, "'\"")
			return val != ""
		}
		return false
	default:
		return false
	}
}

// literalToString converts a literal to string representation
func (cf *ConstantFolder) literalToString(lit *Literal) string {
	switch lit.Type {
	case StringLiteral:
		if str, ok := lit.Value.(string); ok {
			// Remove quotes if present
			if len(str) >= 2 && (str[0] == '\'' || str[0] == '"') {
				return str[1 : len(str)-1]
			}
			return str
		}
		return ""
	case IntegerLiteral, FloatLiteral:
		if str, ok := lit.Value.(string); ok {
			return str
		}
		return fmt.Sprintf("%v", lit.Value)
	case BooleanLiteral:
		if cf.literalToBool(lit) {
			return "true"
		}
		return "false"
	case NullLiteral:
		return "NULL"
	default:
		return fmt.Sprintf("%v", lit.Value)
	}
}

// literalsEqual checks if two literals are equal
func (cf *ConstantFolder) literalsEqual(left, right *Literal) bool {
	// Allow comparison between different numeric types
	if (left.Type == IntegerLiteral || left.Type == FloatLiteral) && 
	   (right.Type == IntegerLiteral || right.Type == FloatLiteral) {
		leftVal, leftErr := cf.literalToFloat(left)
		rightVal, rightErr := cf.literalToFloat(right)
		return leftErr == nil && rightErr == nil && leftVal == rightVal
	}
	
	if left.Type != right.Type {
		return false
	}
	
	// Handle same type comparisons
	switch left.Type {
	case StringLiteral:
		leftStr, leftOk := left.Value.(string)
		rightStr, rightOk := right.Value.(string)
		return leftOk && rightOk && leftStr == rightStr
	case BooleanLiteral:
		return cf.literalToBool(left) == cf.literalToBool(right)
	default:
		return left.Value == right.Value
	}
}

// Function folding helpers

func (cf *ConstantFolder) foldCoalesce(args []Expression) Expression {
	for _, arg := range args {
		if lit, isLit := arg.(*Literal); isLit {
			if lit.Type != NullLiteral {
				return lit
			}
		}
	}
	return &Literal{Value: "NULL", Type: NullLiteral}
}

func (cf *ConstantFolder) foldNullIf(args []Expression) Expression {
	if len(args) != 2 {
		return &FunctionCall{Name: "NULLIF", Args: args}
	}

	left, leftIsLit := args[0].(*Literal)
	right, rightIsLit := args[1].(*Literal)

	if leftIsLit && rightIsLit && cf.literalsEqual(left, right) {
		return &Literal{Value: "NULL", Type: NullLiteral}
	}

	if leftIsLit {
		return left
	}

	return &FunctionCall{Name: "NULLIF", Args: args}
}

func (cf *ConstantFolder) foldGreatest(args []Expression) Expression {
	if len(args) == 0 {
		return &FunctionCall{Name: "GREATEST", Args: args}
	}

	var maxVal float64
	var maxLit *Literal
	first := true

	for _, arg := range args {
		if lit, isLit := arg.(*Literal); isLit {
			if val, err := cf.literalToFloat(lit); err == nil {
				if first || val > maxVal {
					maxVal = val
					maxLit = lit
					first = false
				}
			}
		}
	}

	if maxLit != nil {
		return maxLit
	}

	return &FunctionCall{Name: "GREATEST", Args: args}
}

func (cf *ConstantFolder) foldLeast(args []Expression) Expression {
	if len(args) == 0 {
		return &FunctionCall{Name: "LEAST", Args: args}
	}

	var minVal float64
	var minLit *Literal
	first := true

	for _, arg := range args {
		if lit, isLit := arg.(*Literal); isLit {
			if val, err := cf.literalToFloat(lit); err == nil {
				if first || val < minVal {
					minVal = val
					minLit = lit
					first = false
				}
			}
		}
	}

	if minLit != nil {
		return minLit
	}

	return &FunctionCall{Name: "LEAST", Args: args}
}